package com.andaily.service.impl;

import com.andaily.domain.account.Account;
import com.andaily.domain.account.AccountRepository;
import com.andaily.domain.account.AccountType;
import com.andaily.domain.account.actions.AccountMonthChartDtoLoader;
import com.andaily.domain.account.actions.AccountMonthStatisticsDTOLoader;
import com.andaily.domain.dto.account.*;
import com.andaily.domain.shared.Application;
import com.andaily.domain.shared.paginated.PaginatedLoader;
import com.andaily.domain.shared.security.SecurityUtils;
import com.andaily.domain.user.AccountSetting;
import com.andaily.domain.user.User;
import com.andaily.domain.user.UserSetting;
import com.andaily.infrastructure.mybatis.data.AccountStatisticsData;
import com.andaily.infrastructure.support.LogHelper;
import com.andaily.service.AccountService;
import com.andaily.service.UserService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Shengzhao Li
 */
public class AccountServiceImpl implements AccountService {

    private static LogHelper log = LogHelper.create(AccountServiceImpl.class);

    @Autowired
    private AccountRepository accountRepository;
    @Autowired
    private UserService userService;

    @Override
    public AccountFormDTO loadAccountFormDTO(String accountGuid) {
        log.debug("Call loadAccountFormDTO, accountGuid = " + accountGuid);
        AccountFormDTO accountFormDTO;
        if (StringUtils.isNotBlank(accountGuid)) {
            Account account = accountRepository.findByGuid(accountGuid);
            accountFormDTO = new AccountFormDTO(account);
        } else {
            accountFormDTO = new AccountFormDTO();
        }
        return accountFormDTO;
    }

    /**
     * 非登录时账务的查询统计对于非公开的账务类型不显示
     *
     * @return AccountTypeDTO list
     */
    @Override
    public List<AccountTypeDTO> loadAvailableAccountTypes() {
        List<AccountType> accountTypes = new ArrayList<AccountType>();
        boolean logged = SecurityUtils.isLogged();

        AccountType defaultType = accountRepository.findAccountTypeByGuid(Application.defaultAccountTypeGuid());
        accountTypes.add(defaultType);
        if (logged) {
            String userGuid = SecurityUtils.currentUserGuid();
            accountTypes.addAll(accountRepository.findMyAccountTypes(userGuid));
        }
        //add default type
        accountTypes.addAll(accountRepository.findDefaultAccountTypes(logged));
        return AccountTypeDTO.toDTOList(accountTypes);
    }

    @Override
    public void persistAccount(AccountFormDTO accountFormDTO) {
        String typeGuid = accountFormDTO.getAccountType().getGuid();
        AccountType accountType = accountRepository.findAccountTypeByGuid(typeGuid);

        Account account;
        String guid = accountFormDTO.getGuid();
        if (StringUtils.isNotBlank(guid)) {
            log.debug("Update Account by guid = " + guid);
            account = accountRepository.findByGuid(guid);
            //update used times
            AccountType oldType = account.type();
            if (!accountType.equals(oldType)) {
                updateAccountTypeUsedTimes(oldType.guid(), oldType.usedTimes() - 1);
                updateAccountTypeUsedTimes(typeGuid, accountType.usedTimes() + 1);
            }
            account.update(accountFormDTO, accountType);
            accountRepository.updateAccount(account);
        } else {
            log.debug("Create an Account from " + accountFormDTO);
            User owner = userService.currentUser();
            account = accountFormDTO.toAccount(accountType, owner);
            accountRepository.saveAccount(account);
            updateAccountTypeUsedTimes(typeGuid, accountType.usedTimes() + 1);
        }
    }

    @Override
    public void updateAccountTypeUsedTimes(String guid, int newTimes) {
        int times = (newTimes < 0 ? 0 : newTimes);
        accountRepository.updateAccountTypeUsedTimes(times, guid);
    }

    @Override
    public AccountOverviewDTO loadAccountOverview(final AccountOverviewDTO accountOverviewDTO) {
        log.debug("Load Account overview data by " + accountOverviewDTO);
        //update user account setting if logged
        if (SecurityUtils.isLogged()) {
            log.debug("User is logged, update User Account setting");
            UserSetting userSetting = userService.loadUserSettingByUserGuid(SecurityUtils.currentUserGuid());
            accountOverviewDTO.updateSetting(userSetting.accountSetting());
        }
        //set total value
        int totalAccountSize = loadTotalAccount();
        accountOverviewDTO.setTotal(totalAccountSize);

        final Map<String, Object> queryParamMap = accountOverviewDTO.queryParamMap();
        log.debug("Query Account params " + queryParamMap);
        AccountOverviewDTO overviewDTO = accountOverviewDTO.load(new PaginatedLoader<AccountDTO>() {
            @Override
            public List<AccountDTO> loadList() {
                List<Account> list = accountRepository.findByPaginated(queryParamMap);
                return AccountDTO.toDTOList(list);
            }

            @Override
            public int loadTotalSize() {
                return accountRepository.findTotalSizeByPaginated(queryParamMap);
            }
        });
        overviewDTO.updateShowMore();
        return overviewDTO;
    }

    @Override
    public int loadTotalAccount() {
        return accountRepository.totalAccountSize();
    }

    @Override
    public AccountDTO loadByGuid(String guid) {
        Account account = accountRepository.findByGuid(guid);
        return new AccountDTO(account);
    }

    @Override
    public void archivedAccount(String guid) {
        accountRepository.archiveAccountByGuid(guid);
    }

    @Override
    public AccountTypeOverviewDTO loadAccountTypeOverview(AccountTypeOverviewDTO accountTypeOverviewDTO) {
        //default
        List<AccountType> defaultAccountTypes = accountRepository.findDefaultAccountTypes(true);
        List<AccountTypeDTO> accountTypeDTOList = AccountTypeDTO.toDTOList(defaultAccountTypes);
        accountTypeOverviewDTO.setDefaultAccountTypes(accountTypeDTOList);
        //self
        String myGuid = SecurityUtils.currentUserGuid();
        List<AccountType> myAccountTypes = accountRepository.findMyAccountTypes(myGuid);
        accountTypeOverviewDTO.setSelfAccountTypes(AccountTypeDTO.toDTOList(myAccountTypes));
        return accountTypeOverviewDTO;
    }

    @Override
    public AccountTypeDTO loadAccountType(String guid) {
        AccountTypeDTO accountTypeDTO;
        if (StringUtils.isEmpty(guid)) {
            accountTypeDTO = new AccountTypeDTO(true, false);
        } else {
            AccountType accountType = accountRepository.findAccountTypeByGuid(guid);
            accountTypeDTO = new AccountTypeDTO(accountType);
        }
        return accountTypeDTO;
    }

    @Override
    public void persistAccountType(AccountTypeDTO accountType) {
        String guid = accountType.getGuid();
        if (StringUtils.isEmpty(guid)) {
            User user = userService.currentUser();
            AccountType newType = accountType.toType(user);
            accountRepository.saveAccountType(newType);
        } else {
            AccountType type = accountRepository.findAccountTypeByGuid(guid);
            type.updateByDTO(accountType);
            accountRepository.updateAccountType(type);
        }
    }

    @Override
    public void archivedAccountType(String guid) {
        log.debug("Archived AccountType by guid = " + guid);
        accountRepository.archiveAccountTypeByGuid(guid);
    }

    @Override
    public AccountSearchResultDTO searchAccount(AccountSearchDTO accountSearchDTO) {
        AccountSearchResultDTO resultDTO = new AccountSearchResultDTO(accountSearchDTO);
        final Map<String, Object> queryParams = resultDTO.queryParamMap();
        resultDTO.load(new PaginatedLoader<AccountDTO>() {
            @Override
            public List<AccountDTO> loadList() {
                List<Account> accounts = accountRepository.findAccounts(queryParams);
                return AccountDTO.toDTOList(accounts);
            }

            @Override
            public int loadTotalSize() {
                return accountRepository.findTotalSize(queryParams);
            }
        });
        BigDecimal totalAmount = accountRepository.findTotalAmount(queryParams);
        resultDTO.setTotalAmount(totalAmount != null ? totalAmount : BigDecimal.ZERO);

        resultDTO.updateShowMore();
        return resultDTO;
    }

    @Override
    public AccountSettingDTO loadAccountSettingDTO() {
        String userGuid = SecurityUtils.currentUserGuid();
        log.debug("Load Account setting from currentUserGuid = " + userGuid);
        List<Account> deletedAccounts = accountRepository.findArchivedAccounts(userGuid);
        log.debug("Load deleted account size: " + deletedAccounts.size());

        UserSetting userSetting = userService.loadUserSettingByUserGuid(userGuid);
        AccountSettingDTO accountSettingDTO = new AccountSettingDTO(userSetting.accountSetting());
        accountSettingDTO.setDeletedAccounts(AccountDTO.toDTOList(deletedAccounts));
        return accountSettingDTO;
    }

    @Override
    public boolean updateAccountSetting(AccountSettingDTO accountSettingDTO) {
        log.debug("Call updateAccountSetting, accountSettingDTO = " + accountSettingDTO);
        String userGuid = SecurityUtils.currentUserGuid();
        AccountSetting accountSetting = accountSettingDTO.toAccountSetting();
        userService.updateAccountSettingByUserGuid(accountSetting, userGuid);
        return true;
    }

    @Override
    public void cleanUpAccount(String accountGuid) {
        log.debug("Clean up Account from guid = " + accountGuid);
        accountRepository.removeAccount(accountGuid);
    }

    @Override
    public boolean revertAccount(String accountGuid) {
        log.debug("Revert Account from guid = " + accountGuid);
        accountRepository.activeAccount(accountGuid);
        return true;
    }

    @Override
    public AccountStatisticsDTO loadAccountStatistics(AccountStatisticsDTO statisticsDTO) {
        log.debug("Load Account statistics by " + statisticsDTO);
        BigDecimal totalAmount = accountRepository.findAllTotalAmount();
        statisticsDTO.setAllTotalAmount(totalAmount);
        //self total
        if (statisticsDTO.isLogged()) {
            Map<String, Object> params = new HashMap<String, Object>();
            params.put("ownerGuid", SecurityUtils.currentUserGuid());
            BigDecimal myTotalAmount = accountRepository.findTotalAmount(params);
            statisticsDTO.setMyTotalAmount(myTotalAmount);
        }
        return statisticsDTO;
    }

    @Override
    public AccountDailyStatisticsDTO loadDailyStatistics(final AccountDailyStatisticsDTO dailyStatisticsDTO) {
        final Map<String, Object> queryParams = dailyStatisticsDTO.queryParams();
        dailyStatisticsDTO.load(new PaginatedLoader<AccountStatisticsData>() {
            @Override
            public List<AccountStatisticsData> loadList() {
                return accountRepository.findDailyStatistics(queryParams);
            }

            @Override
            public int loadTotalSize() {
                return accountRepository.totalDailyAccountSize(queryParams);
            }
        });
        dailyStatisticsDTO.updateShowMore();
        return dailyStatisticsDTO;
    }

    @Override
    public AccountStatisticsDetailsDTO loadStatisticsDetails(final AccountStatisticsDetailsDTO statisticsDetailsDTO) {
        final Map<String, Object> params = statisticsDetailsDTO.queryParamMap();
        statisticsDetailsDTO.load(new PaginatedLoader<AccountDTO>() {
            @Override
            public List<AccountDTO> loadList() {
                List<Account> accounts = accountRepository.findStatisticsAccounts(params);
                return AccountDTO.toDTOList(accounts);
            }

            @Override
            public int loadTotalSize() {
                return accountRepository.totalStatisticsAccounts(params);
            }
        });

        return statisticsDetailsDTO;
    }

    @Override
    public AccountTypeStatisticsDTO loadTypeStatistics(AccountTypeStatisticsDTO typeStatisticsDTO) {
        final Map<String, Object> params = typeStatisticsDTO.queryParams();
        return typeStatisticsDTO.load(new PaginatedLoader<AccountStatisticsData>() {
            @Override
            public List<AccountStatisticsData> loadList() {
                return accountRepository.findTypeStatistics(params);
            }

            @Override
            public int loadTotalSize() {
                return accountRepository.totalTypeStatistics(params);
            }
        });
    }

    @Override
    public AccountTypeChartDto loadTypeChartData() {
        final String userGuid = SecurityUtils.currentUserGuid();
        final AccountTypeChartDto typeChartDto = new AccountTypeChartDto();
        List<AccountStatisticsData> datas = accountRepository.findTypeChartData(userGuid, typeChartDto.getChartDataSize());
        return typeChartDto.updateList(datas);
    }

    @Override
    public AccountDailyChartDto loadDailyChartData() {
        final String userGuid = SecurityUtils.currentUserGuid();
        final AccountDailyChartDto dailyChartDto = new AccountDailyChartDto();
        List<AccountStatisticsData> datas = accountRepository.findDailyChartData(userGuid, dailyChartDto.getChartDataSize());
        return dailyChartDto.updateList(datas);
    }

    @Override
    public AccountPaymentsStatisticsDto loadAccountPaymentsStatisticsDto() {
        final String userGuid = SecurityUtils.currentUserGuid();
        final String incomeTypeGuid = Application.incomeAccountTypeGuid();
        AccountStatisticsData incomeData = accountRepository.findIncomeStatisticsData(userGuid, incomeTypeGuid);
        AccountStatisticsData expenditureData = accountRepository.findExpenditureStatisticsData(userGuid, incomeTypeGuid);
        return new AccountPaymentsStatisticsDto(incomeData, expenditureData);
    }

    @Override
    public AccountMonthStatisticsDTO loadMonthStatistics(AccountMonthStatisticsDTO monthStatisticsDTO) {
        AccountMonthStatisticsDTOLoader accountMonthStatisticsDTOLoader = new AccountMonthStatisticsDTOLoader(monthStatisticsDTO);
        return accountMonthStatisticsDTOLoader.load();
    }

    @Override
    public AccountMonthChartDto loadMonthChartData() {
        AccountMonthChartDtoLoader monthChartDtoLoader = new AccountMonthChartDtoLoader();
        return monthChartDtoLoader.load();
    }
}
